/*
 * ssl_client.c
 *
 * Copyright (C) 2011 - Unknown
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <memory.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <openssl/ssl.h>
#include <sys/errno.h>
#include <netdb.h>

#include "../terminals.h"

void help(int output);
void parse(char* cmd, int output);

int main(int argc, char* argv[]) {
	int sock, res;
	socklen_t peer_addr_size;
	fd_set fds;
	char* res_str, buffer[4096];
	struct sockaddr_in my_addr, peer_addr;
	SSL_METHOD* ssl_method;
	SSL_CTX* ssl_ctx;
	SSL* ssl;
	terminal_t* term;
	struct hostent* hostinfo = NULL;
	// Parametri
	int port = 1234;
	char* host;

	if (argc != 3) {
		fprintf(stderr, "Usage:\n");
		fprintf(stderr, "%s HOST PORT\n", argv[0]);
		exit(1);
	}

	host = argv[1];
	if (sscanf(argv[2], "%d", &port) != 1) {
		fprintf(stderr, "ERROR: invalid port specified: %s\n", argv[2]);
		exit(1);
	}
	
	SSL_library_init(); /* load encryption & hash algorithms for SSL */
	SSL_load_error_strings(); /* load the error strings for good error reporting */

	// Setting SSL context
	ssl_method = TLSv1_client_method();
	ssl_ctx = SSL_CTX_new(ssl_method);

	// Loading server certificate chain
	fprintf(stderr, "INFO: ssl_client: loading client certificate\n");
	sprintf(buffer, "%s/.casper/ssl/client-cert.pem", getenv("HOME"));
	if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, buffer)) {
		fprintf(stderr, "ERROR: ssl_client: unable to use the specified certificate: %s\n", buffer);
		ERR_print_errors_fp(stderr);
		return 1;
	}

	// Loading private key
	fprintf(stderr, "INFO: ssl_client: loading client private key\n");
	sprintf(buffer, "%s/.casper/ssl/client-key.pem", getenv("HOME"));
	/*res_str = getenv("CASPER_SSL_PKEY_FILE");
	if (res_str == NULL) {
		fprintf(stderr, "ERROR: ssl_client: unable to find CASPER_SSL_PKEY_FILE variable\n");
		return 1;
	}*/
	if (!SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, buffer, SSL_FILETYPE_PEM)) {
		fprintf(stderr, "ERROR: ssl_client: unable to load the specified private key: %s\n", buffer);
		ERR_print_errors_fp(stderr);
		return 1;
	}

	// Setting peer verification on
	SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);

	fprintf(stderr, "INFO: ssl_client: loading trusted CA certificates\n");
	sprintf(buffer, "%s/.casper/ssl/cacert.pem", getenv("HOME"));
	if (SSL_CTX_load_verify_locations(ssl_ctx, buffer, NULL) != 1) {
		fprintf(stderr, "ERROR: ssl_client: unable to read CA certificates from %s\n", buffer);
		ERR_print_errors_fp(stderr);
		return 1;
	}

	// Creating the SSL structure
	ssl = SSL_new(ssl_ctx);
	if (ssl == NULL) {
		fprintf(stderr, "ERROR: ssl_client: unable to create the SSL structure\n");
		ERR_print_errors_fp(stderr);
		return 1;
	}

	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sock == -1) {
		fprintf(stderr, "ERROR: ssl_client: socket() failed: unable to create TCP socket (errno: %d)\n", errno);
		fprintf(stderr, "ERROR: -> %s\n", strerror(errno));
		return errno;
	}

	memset(&my_addr, 0, sizeof(struct sockaddr_in));
	my_addr.sin_family = AF_INET;
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	my_addr.sin_port = 0;
	if (bind(sock, (struct sockaddr*) &my_addr, sizeof(my_addr)) < 0) {
		fprintf(stderr, "ERROR: unable to bind local socket (errno: %d)\n", errno);
		fprintf(stderr, "ERROR: -> %s\n", strerror(errno));
		return errno;
	}

	/* assign our destination address */
	memset ( &peer_addr, 0, sizeof(peer_addr) );
	peer_addr.sin_family = AF_INET;
	peer_addr.sin_addr.s_addr = inet_addr(host);
	if (peer_addr.sin_addr.s_addr == -1) {
		hostinfo = gethostbyname2(host, AF_INET);
		if (hostinfo == NULL || hostinfo->h_addr == NULL) {
			fprintf(stderr, "ERROR: ssl_client: unable to resolve %s\n", host);
			return 1;
		}
		fprintf(stderr, "INFO: ssl_client: %s resolved to %s\n", hostinfo->h_name, inet_ntoa(*((struct in_addr*)hostinfo->h_addr)));
		peer_addr.sin_addr.s_addr = *((in_addr_t*)hostinfo->h_addr);
	}
	peer_addr.sin_port = htons(port);

	fprintf(stderr, "INFO: ssl_client: connecting to %s:%d\n", inet_ntoa(peer_addr.sin_addr), port);
	if (connect(sock, (struct sockaddr*) &peer_addr, sizeof(peer_addr)) < 0) {
		fprintf(stderr, "ERROR: unable to connect to %s:%d (errno: %d)\n", host, port, errno);
		fprintf(stderr, "ERROR: -> %s\n", strerror(errno));
		return errno;
	}

	SSL_set_fd(ssl, sock);
	res = SSL_connect(ssl);
	if (res != 1) {
		printf("Result: %d\n", res);
		ERR_print_errors_fp(stderr);
		exit(1);
	}
	printf("INFO: ssl_client: connected to %s:%d\n", inet_ntoa(peer_addr.sin_addr), port);

	setvbuf(stdout,NULL,_IONBF,0);

	term = terminal_new_socket(sock);
	terminal_set_iofcnts (term, ssl, SSL_read, SSL_write);

	while (1) {
		FD_ZERO(&fds);
		FD_SET(sock, &fds);
		FD_SET(0, &fds);

		printf("\e[1;31m$ \e[0m");

		res = select ((sock > 0 ? sock : 0) + 1, &fds, NULL, NULL, NULL);
		if (res == -1) {
			fprintf(stderr, "ERROR: ssl_client: select() failed: exiting\n");
			return 1;
		}

		if (FD_ISSET(0, &fds)) {
			res = read(0, buffer, 4095);
			if (res <= 0) {
				fprintf(stderr, "INFO: read() read nothing: exiting\n");
				return 0;
			}
			parse(buffer, 1);
			res = SSL_write(ssl, buffer, res);
			if (res <= 0) {
				fprintf(stderr, "INFO: connection closed: exiting\n");
				return 0;
			}
		}
		if (FD_ISSET(sock, &fds)) {
			do {
				res_str = terminal_read_from_daemon(term);
				if (res_str == NULL) {
					fprintf(stderr, "INFO: connection closed: exiting\n");
					return 0;
				}
				printf("\b\b\e[0;32m%s\e[0m\n", res_str);
				free(res_str);
			} while (terminal_has_more_for_terminal(term));
		}
	}
}

void parse(char* cmd, int output) {
	if (!strncmp(cmd, "exit", 4))
		exit(0);
	else if (!strncmp(cmd, "help", 4))
		help(output);
}

void help(int output) {
	printf(" * exit\n");
}

